Observer Pattern: example
画面上のUIに、レーダーミニマップを実装することで
Observer Patternを理解する。
UIの準備
1. リソースを読み込む
2. Canvasを選択
3. Create > UI > Image
4. レーダーのテクスチャ素材をAsset Libraryにドラッグ&ドロップ
5. テクスチャを選択 > Inspector > Texture Type > Sprite(2D and UI)
https://gyazo.com/693360e4338641736f0aa5d1a76fe3be
6. Apply
7. ImageにSpriteをセット
8. Alt 押しながら、 Anchorを左下にセット
9. Imageを入れ子にしつつ、こんな感じにする
https://gyazo.com/a76f06afa00c590eccb74eb4d7123fd6
10. Eggを削除
実行時に動的に追加する
Mask Component
Rect Transform系座標で
Canvas Rendererで描写されるオブジェクトに有効
MaskコンポーネントがアタッチされたUIの子要素UIは、
親要素からはみ出ると描写されなくなる
(透明度がかかってる部分も同様)
Egg Spawner
code: .fs
type Spawner() =
inherit MonoBehaviour()
val mutable eggPrefab: GameObject
val mutable terrain: Terrain
let mutable terrainData: TerrainData = null
member this.CreateEgg() =
let x = Random.Range(0.0f, terrainData.size.x)
let z = Random.Range(0.0f, terrainData.size.z)
let xyPos = Vector3(x, 0.0f, z)
let pos =
Vector3(x, this.terrain.SampleHeight(xyPos) + 10.0f, z)
GameObject.Instantiate(this.eggPrefab, pos, Quaternion.identity)
member this.Start() =
do terrainData <- this.terrain.terrainData
this.InvokeRepeating("CreateEgg", 1.0f, 0.1f)
member this.Update() = ()
1秒間ごとに、卵をスポーンするプログラム
MonoBehaviour.InvokeRepeating()
初回呼び出しをtime秒後に行い、repeatRate毎に methodNameを呼び出す
GameObject.Instantiate()
static メソッド
GameObjectをインスタンス化する。
第4引数を省略すると、親をセットしないでトップレベルに追加される。
x.0f
float32()のショートハンド
Event class & Event Listener Class
code: .fs
open UnityEngine.Events
type UnityGameObjectEvent() =
inherit UnityEvent<GameObject>()
type Event() =
inherit ScriptableObject()
let mutable elisteners: list<EventListener> = []
member this.Register(listener) = elisteners <- listener :: elisteners
member this.UnRegister(listener: EventListener) = elisteners <- (List.filter (fun l -> l <> listener) elisteners)
member this.Occuerred(go: GameObject) = List.iter (fun (l: EventListener) -> l.OnEventOccurs(go)) elisteners
and EventListener() =
inherit MonoBehaviour()
val mutable gEvent: Event
val mutable response: UnityGameObjectEvent
member this.OnEnable() = this.gEvent.Register(this)
member this.OnDisable() = this.gEvent.UnRegister(this)
member this.OnEventOccurs(go: GameObject) = this.response.Invoke(go)
これらのクラスは、お互いを参照する循環参照クラスなので、
andキーワードを使って連続してtype宣言する。
Event オブジェクト
Scriptable Objectで、イベントオブジェクトを作る。
複数のEventListnerから参照される。
多分、Scriptable Object のインスタンスは一つだけ
イベントを起こす側、監視する側のClassはどちらも
Event オブジェクトへの参照を持つ必要がある。
SOは内部状態をもつ
elisners : このイベントを購読しているリスナー達
メソッド
Register, UnRegister : リスナーを登録、解除
Occuered(go) : 状態として保持しているリスナーリスト全員に、リスナーに実装されているOnOccueredメソッドを実行させる
EventListner
Eventオブジェクトを利用する側
UnityEvent class
こちらはユーザー定義ではなく、Unityに組み込まれてるやつ
特定のクラスに特化させられるジェネリック型
UnityEvent<GameObject>
SerializeField にすると、Inspector上で編集できる。
unityEvent.Invoke(arg)で、イベントを起こせる。
特徴として、イベントを起こす際にInspectorで指定したメソッドが呼び出されるが、
ゲームオブジェクトにアタッチしてあるコンポーネントすべての中から選べる。
他のコンポーネントのハブとして使える。
https://gyazo.com/12cb4dfaeb357a045a9d3708912b270b
ちなみに、Invokeする対象のGameObjectも任意
ここでは、ヒエラルキーからRadar(Listner)自身を持ってきている。
List.iter
関数を、リストの要素全てに適用する。
戻り値はunit
forEachみたいな感じ。
型推論ができないらしく、ラムダ式の引数に型ラベルがいる
Radar Component
最初から、用意してあるやつ
OnDroppedメソッドを実装して、
EggDrop Scriptable Object の Occueredイベントが起こったら、
EventListenrから呼び出す。
goを引数として受け取れるので、
ゲームオブジェクトへの参照(owner)
レーダー上の画像(Image UI)
をリストとして保持し、
ゲームオブジェクトとプレーヤーの位置をレーダー座標に変換して
毎フレームアップデートする。
複数アイコンに対応する
今回は卵だけでやったが、
本来は
ゴール
スタート
エネミー
オブジェクト
武器
などなど、様々なものに対応できるように
異なるオブジェクトは異なるアイコンを表示させたい
そのために、イベントとアイコンを設定できるクラスを作る
code: .fs
type Item() =
inherit MonoBehaviour()
val mutable dropped: Event
val mutable icon: Image
member this.Start() = this.dropped.Occuerred(this.gameObject)
ついでに、インスタンス化のときにイベントを起こす処理もこっちのStart()で書く
使うときは、GetComponent<T>で取得
code: .fs
public void ItemDropped(GameObject go)
{
Debug.Log("Item Dropped");
RegisterRadarObject(go, go.GetComponent<Item>().icon);
}
DefaultValueAttribute
C#と違って、[<DefaultValueAttribute>]でも属性を指定できる
Attributeがつくかつかないかの違いだが、補完で最初に出るのはこっち
open module
今回のスクリプトでは、EventとUIに関するUnity機能を使ってるので、
モジュール名を省略する場合はopenキーワードを使う
code: .fs
open UnityEngine.UIElements
open UnityEngine.Events
アイテムの追加
Eggの他に、MediKitを作って
Script
Prefab
Icon
をそれぞれ追加する。
Spawnerで、Egg以外も作れるようにする。
イベントの追加
イベントを追加するには、
Scriptableオブジェクトを追加して、EventListnerでもメソッドを追加する。
droppedイベントが既に作ってあるので、pickupも実装してみる
OncollisionEnter
MeshRenderer を false
Collider を false
Destroy を 5秒後
あたったらすぐに無効化しないと、チャタリングする可能性あり
アイテム側に実装する。
EventMessage
Canvasに、Text要素を追加して
EventListenrerから呼び出すインターフェースを実装して
イベント発生時に呼び出せばOK